1 module hip.font.bmfont;
2 import hip.api.data.font;
3 import hip.api.renderer.texture;
4 import hip.error.handler;
5 
6 
7 class HipBitmapFont : HipFont
8 {
9     ///The atlas path is saved inside the class
10     string atlasPath;
11     ///This variable is defined when the atlas is being read
12     string atlasTexturePath;
13 
14     ///Use that property to know how many characters was read inside the atlas
15     uint charactersCount;
16 
17     uint height;
18 
19     private string error;
20 
21 
22     HipFontKerning kerning;
23 
24     bool loadAtlas(string data, string atlasPath)
25     {
26         import hip.util.string;
27         this.atlasPath = atlasPath;
28 
29         static int advanceSpace(string data, int i)
30         {
31             while(i < data.length && (data[i].isWhitespace || data[i] == '='))
32                 i++;
33             return i;
34         }
35         static int nextToken(string data, int i)
36         {
37             i = advanceSpace(data, i);
38             if(i >= data.length)
39                 return -1;
40             else if(data[i] == '"') //Find the '"'
41             {
42                 i++;
43                 while(i < data.length && data[i] != '"')
44                 {
45                     if(data[i] == '\\')
46                         i++;
47                     i++;
48                 }
49                 if(i < data.length)i++;
50                 
51             }
52             else if(data[i].isAlpha)
53             {
54                 while(i <= data.length && data[i].isAlpha)
55                     i++;
56             }
57             else if(data[i].isNumeric)
58             {
59                 while(i <= data.length && data[i].isNumeric)
60                     i++;
61             }
62             return i;
63         }
64 
65         static int getNextInt(string data, ref int i)
66         {
67             import hip.util.conv;
68             int start = advanceSpace(data, i);
69             i = nextToken(data, start);
70             if(i != -1)
71             {
72                 return to!int(data[start..i]);
73             }
74             return i;
75         }
76 
77         static string getNextString(string data, ref int i)
78         {
79             int start = advanceSpace(data, i);
80             i = nextToken(data, start);
81             if(i != -1)
82             {
83                 if(data[start] == '"')
84                     return data[start+1..i-1];
85                 else
86                     return data[start..i];
87             }
88             return "";
89         }
90         string name;
91         int size;
92 
93         int bold, italic;
94         string charset;
95         int unicode;
96         int stretchH;
97         int smooth, aa;
98 
99         int paddingX, paddingY, paddingW, paddingH;
100         int spacingX, spacingY;
101         int outline;
102 
103         //Common
104         int lineHeight, base, scaleW, scaleH, pages, packed, alpha, red, green,blue;
105         //Page
106         int pageId;
107         //chars
108         int count;
109 
110         int index = 0;
111         enum Context
112         {
113             info,
114             common,
115             page,
116             chars,
117             kernings,
118             unknown
119         }
120         int context = Context.unknown;
121         string key;
122         while(index != -1 && index < data.length)
123         {
124             key = getNextString(data, index);
125             final switch(context)
126             {
127                 case Context.info:
128                 {
129                     switch(key)
130                     {
131                         case "face": 
132                             name = getNextString(data, index);
133                             break;
134                         case "size":
135                             size = getNextInt(data, index);
136                             height = size;
137                             break;
138                         case "bold":
139                             bold = getNextInt(data, index);
140                             break;
141                         case "italic":
142                             italic = getNextInt(data, index);
143                             break;
144                         case "charset":
145                             charset = getNextString(data, index);
146                             break;
147                         case "unicode":
148                             unicode = getNextInt(data, index);
149                             break;
150                         case "stretchH":
151                             stretchH = getNextInt(data, index);
152                             break;
153                         case "smooth":
154                             smooth = getNextInt(data, index);
155                             break;
156                         case "aa":
157                             aa = getNextInt(data, index);
158                             break;
159                         case "padding":
160                             paddingX = getNextInt(data, index);
161                             index++;
162                             paddingY = getNextInt(data, index);
163                             index++;
164                             paddingW = getNextInt(data, index);
165                             index++;
166                             paddingH = getNextInt(data, index);
167                             index++;
168                             break;
169                         case "spacing":
170                             spacingX = getNextInt(data, index);
171                             index++;
172                             spacingY = getNextInt(data, index);
173                             index++;
174                             break;
175                         case "outline":
176                             outline = getNextInt(data, index);
177                             break;
178                         default:
179                             goto checkUnknown;
180                     }
181                     break;
182                 }
183                 case Context.common:
184                 {
185                     int tempIndex = index;
186                     int next = getNextInt(data, index);
187                     switch(key)
188                     {
189                         case "lineHeight":
190                             lineHeight = next;
191                             break;
192                         case "base":
193                             base = next;
194                             break;
195                         case "scaleW":
196                             scaleW = next;
197                             break;
198                         case "scaleH":
199                             scaleH = next;
200                             break;
201                         case "pages":
202                             pages = next;
203                             break;
204                         case "packed":
205                             packed = next;
206                             break;
207                         case "alphaChnl":
208                             alpha = next;
209                             break;
210                         case "redChnl":
211                             red = next;
212                             break;
213                         case "greenChnl":
214                             green = next;
215                             break;
216                         case"blueChnl":
217                             blue = next;
218                             break;
219                         default:
220                             index = tempIndex;
221                             goto checkUnknown;
222                     }
223                     break;
224                 }
225                 case Context.page:
226                 {
227                     switch(key)
228                     {
229                         case "id":
230                             pageId = getNextInt(data, index);
231                             break;
232                         case "file":
233                             atlasTexturePath = getNextString(data, index);
234                             break;
235                         default:
236                             goto checkUnknown;
237                     }
238                     break;
239                 }
240                 case Context.chars:
241                 {
242                     //Advance "count"
243                     charactersCount = count = getNextInt(data, index);
244                     uint maxWidth = 0;
245 
246 
247                     for(int i = 0; i < count; i++)
248                     {
249                         HipFontChar ch;
250                         int*[10] fields = [cast(int*)&ch.id, &ch.x, &ch.y, &ch.width, &ch.height, &ch.xoffset, &ch.yoffset, &ch.xadvance, &ch.page, &ch.chnl];
251                         //Advance "char"
252                         index = nextToken(data, index);
253                         for(int fIndex = 0; fIndex < fields.length; fIndex++)
254                         {
255                             index = nextToken(data, index);
256                             *fields[fIndex] = getNextInt(data, index);
257                         }
258                         if(ch.width > maxWidth)
259                             maxWidth = ch.width;
260                         characters[ch.id] = ch;
261                     }
262                     auto space = ' ' in characters;
263                     if(space is null || (space.width == 0 && space.xadvance == 0))
264                         spaceWidth = maxWidth;
265                     else
266                         spaceWidth = space.xadvance > space.width ? space.xadvance : space.width;
267                     lineBreakHeight = lineHeight;
268                     context = Context.unknown;
269                     break;
270                 }
271                 case Context.kernings:
272                 {
273                     //Advance "count"
274                     index = nextToken(data, index);
275                     int kerningCount = getNextInt(data, index);
276                     for(int i = 0; i < kerningCount; i++)
277                     {
278                         //Advance "kerning "
279                         index = nextToken(data, index);
280                         int[3] values = void; //first, second, amount
281                         for(int fIndex = 0; fIndex < 3; fIndex++)
282                         {
283                             index = nextToken(data, index);
284                             values[fIndex] = getNextInt(data, index);
285                         }
286                         if((values[0] in kerning) is null)
287                             kerning[values[0]] = HipCharKerning.init;
288                         kerning[values[0]][values[1]] = values[2];
289                     }
290                     break;
291                 }
292                 //Tries to find the context
293                 checkUnknown: case Context.unknown:
294                     contextSwitch: switch(key)
295                     {
296                         static foreach(mem; __traits(allMembers, Context))
297                         {
298                             case mem:
299                                 context = __traits(getMember, Context, mem);
300                                 break contextSwitch;
301                         }
302                         default:
303                             assert(false, "Unknown key received: "~key);
304                     }
305                     continue;
306             }
307         }
308         
309 
310         return true;
311     }
312 
313     bool loadTexture(IHipTexture t)
314     {
315         texture = t;
316         int width = t.getWidth;
317         int height = t.getHeight;
318         if(width == 0 || height == 0)
319             return false;
320         
321         foreach(ref ch; characters)
322         {
323             if(ch.id != 0)
324             {
325                 ch.normalizedX = cast(float)ch.x/width;
326                 ch.normalizedY = cast(float)ch.y/height;
327                 ch.normalizedWidth = cast(float)ch.width/width;
328                 ch.normalizedHeight = cast(float)ch.height/height;
329             }
330         }
331         return true;
332     }
333 
334     override uint getHeight() const { return height; }
335 
336     string getTexturePath()
337     {
338         import hip.util.path;
339         string texturePath;
340         if(atlasTexturePath != "")
341         {
342             string atlasDir = atlasPath.dirName;
343             if(atlasDir != atlasPath)
344                 texturePath = atlasDir.joinPath(atlasTexturePath);
345             else
346                 texturePath = atlasTexturePath;
347         }
348         return texturePath;
349     }
350 
351     /**
352     *   This won't do anything in case of a bitmap font, as no one can change it.
353     */
354     override HipFont getFontWithSize(uint size)
355     {
356         HipBitmapFont ret = new HipBitmapFont();
357         ret.atlasPath = this.atlasPath;
358         ret.atlasTexturePath = this.atlasTexturePath;
359         ret.kerning = cast(HipFontKerning)this.kerning;
360         ret.charactersCount = this.charactersCount;
361         ret._texture = cast(IHipTexture)this._texture;
362         return cast(HipFont)ret;
363     }
364     void readTexture(string texturePath = "")
365     {
366         texturePath = texturePath == "" ? getTexturePath() :  texturePath;
367         // auto t = new HipTexture();
368         // t.load(texturePath);
369         // loadTexture(t);
370     }
371 
372 
373     static HipBitmapFont fromFile(string atlasPath, string texturePath = "")
374     {
375         auto ret = new HipBitmapFont();
376         ret.loadAtlas("", atlasPath);
377         ret.readTexture(texturePath);
378         return ret;
379     }
380     
381     override int getKerning(const(HipFontChar)* current, const(HipFontChar)* next) const
382     {
383         return getKerning(dchar(current.id), dchar(next.id));
384     }
385     override int getKerning(dchar current, dchar next) const
386     {
387         const HipCharKerning* chKerning = current in kerning;
388         if(chKerning is null)
389             return 0;
390         const int* kerningValue = next in (*chKerning);
391         if(kerningValue is null)
392             return 0;
393         return *kerningValue;
394     }
395     
396 
397 }